﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Threading;
using System.Net.NetworkInformation;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
//using System.IO;
using Innovox.NetTools;
using MvvmFoundation.Wpf;

namespace Innovox.YDeviceLib
{
    public class YDeviceUpdateProgressedEventArgs : EventArgs
    {
        public int LinesUpgraded { get; set; }
        public YDeviceUpdateProgressedEventArgs()
        {

        }
    }
	/*
    public class YDeviceUpdateCompletedEventArgs : EventArgs
    {
        public bool Success { get; set; }

        public YDeviceUpdateCompletedEventArgs()
        {

        }
    }*/

    public class YDevice : ObservableObject, IYDeviceInfo
	{
        // Fired when firmware upgrade has progressed
        public event EventHandler<YDeviceUpdateProgressedEventArgs> YDeviceUpgradeProgressed;
        // Fired when firmware upgrade has completed
        // public event EventHandler<YDeviceUpdateCompletedEventArgs> YDeviceUpgradeCompleted;
        public string IpStr { get { return Ip.ToString(); } }

        #region IYDeviceInfo Interface
        public IPAddress Ip { get; set; }
        public string Mac { get { return Modem.MacAddress; } set { Modem.MacAddress = value; } }
        string _serverName = "";
        public string ServerName
        {
            get { return _serverName; }

            set
            {
                if (_serverName.Equals(value))
                    return;

                _serverName = value;

                base.RaisePropertyChanged("ServerName");
            }
        }

        string _name = "";
        public string Name
        {
            get { return _name; }

            set
            {
                if (_name.Equals(value))
                    return;

                _name = value;

                base.RaisePropertyChanged("Name");
            }
        }

        int _txBitrate = 0;
        public int TxBitrate
        {
            get { return _txBitrate; }

            set
            {
                if (_txBitrate.Equals(value))
                    return;

                _txBitrate = value;

                base.RaisePropertyChanged("TxBitrate");
            }
        }

        int _rxBitrate = 0;
        public int RxBitrate
        {
            get { return _rxBitrate; }

            set
            {
                if (_rxBitrate.Equals(value))
                    return;

                _rxBitrate = value;

                base.RaisePropertyChanged("RxBitrate");
            }
        }

        int _lowBitrate = 0;
        public int LowBitrate 
        {
            get
            {/*
                int tx,rx;
                tx = int.Parse(TxBitrate);
                rx = int.Parse(RxBitrate);

                if (tx > rx)
                    return rx;
                else
                    return tx;*/
                return _lowBitrate;
            }
            set
            {
                if (_lowBitrate == value)
                    return;
                _lowBitrate = value;
                base.RaisePropertyChanged("LowBitrate");
            }

        }

        public string ModemNotSelectedCouplingTypeName
        {
            get { return Modem.GetNotSelectedCouplingName(); }
        }

        string _ping = "";
        public string Ping
        {
            get { return _ping; }

            set
            {
                if (_ping.Equals(value))
                    return;

                _ping = value;

                base.RaisePropertyChanged("Ping");
            }
        }

        string _firmwareVersion = "";
        public string FirmwareVersion
        {
            get { return _firmwareVersion; }

            set
            {
                if (_firmwareVersion.Equals(value))
                    return;

                _firmwareVersion = value;

                base.RaisePropertyChanged("FirmwareVersion");
            }
        }

        public bool IsModemConfigurable()
        {
            if (Modem.AccesType == ModemAccessType.Master || Modem.AccesType == ModemAccessType.Repeater)
                return true;
            else
                return false;
        }
        #endregion

        public DS2Modem Modem { get; set; }
		
		public string Location { get; set; }
        public string[] UpgradeLines { get; set; }
		public List<ObixObject> BatchInObjects { get; set; }
		public List<ObixObject> BatchOutObjects { get; set; }
        public ObixEntry[] ObixEntries { get; set; }
        public bool PirInstalled { get; set; }
        public string ErrorMessage;
        public bool Connected { get; set; }

		public YDevice(string ip)
		{
			Ip = IPAddress.Parse(ip);
			Modem = null;
            PirInstalled = false;
		}

		public YDevice(IPAddress ip)
		{
			Ip = ip;
			Modem = null;
            PirInstalled = false;
		}

        public void UpdateObixData()
        {
            //Thread.Sleep(200);

            if (Modem.AccesType == ModemAccessType.Repeater || Modem.AccesType == ModemAccessType.RepeaterNode)
            {
                string[] uris = new string[] { "/obix/about/serverName/" };
                List<ObixObject> batchOut;
                try
                {
                    batchOut = Read(uris);
                    if (batchOut != null)
                    {
                        string[] values = ObixObject.FindValueAttribute(uris, batchOut);
                        if (values != null)
                        {
                            string server = values[0];

                            if (server != "")
                            {
                                ServerName = server;
                                Modem.AccesType = ModemAccessType.Node;                                
                            }
                        }
                    }
                }
                catch
                {
                    batchOut = null;
                }

                if (batchOut != null && Modem.AccesType == ModemAccessType.Node)
                {
                    uris = new string[] { "/obix/firmwareVersion/", "/obix/name/", "/obix/location/" };
                    try
                    {
                        batchOut = Read(uris);
                    }
                    catch
                    {
                        batchOut = null;
                    }

                    if (batchOut != null)
                    {
                        string[] values = ObixObject.FindValueAttribute(uris, batchOut);
                        if (values != null)
                        {
                            FirmwareVersion = values[0];
                            Name = values[1];
                            Location = values[2];
                        }
                    }
                }
            }
        }

        public static YDevice Create(IPAddress ip)
        {
            DS2Modem mod = DS2Modem.Create(ip);

            if (mod != null)
            {
                //int retries = 3;
                //while (retries > 0)
                {
                    string firmwareVersion = "";
                    string serverName = "";
                    string name = "";
                    string location = "";

                    Thread.Sleep(200);
                   // retries--;

                    if (mod.AccesType == ModemAccessType.Node)
                    {
                        string[] uris = new string[] { "/obix/about/serverName/"};
                        List<ObixObject> batchOut;
                        try
                        {
                            batchOut = YDevice.Read(ip, uris);
                            if (batchOut != null)
                            {
                                string[] values = ObixObject.FindValueAttribute(uris, batchOut);
                                if (values != null)
                                    serverName = values[0];
                            }
                        }
                        catch(Exception ex)
                        {
                            batchOut = null;
                        }

                        if (batchOut != null)
                        {
                            uris = new string[] { "/obix/firmwareVersion/", "/obix/name/", "/obix/location/" };
                            try
                            {
                                batchOut = YDevice.Read(ip, uris);
                            }
                            catch
                            {
                                batchOut = null;
                            }

                            if (batchOut != null)
                            {
                                string[] values = ObixObject.FindValueAttribute(uris, batchOut);
                                if (values != null)
                                {
                                    firmwareVersion = values[0];
                                    //serverName = values[1];
                                    name = values[1];
                                    location = values[2];
                                }
                            }
                        }
                    }
                    else if (mod.AccesType == ModemAccessType.Master || mod.AccesType == ModemAccessType.Repeater)
                        serverName = mod.AccessTypeName;

                    if (mod.AccesType == ModemAccessType.Node && serverName == "")
                    {
                        mod.AccesType = ModemAccessType.RepeaterNode;
                        serverName = mod.AccessTypeName;
                    }

                    YDevice dev;

                    if (serverName == "Y1G")
                        dev = new Y1G(ip);
                    else if (serverName == "YFM")
                      dev = new YFM(ip);
                    else
                        dev = new YDevice(ip);

                    dev.Modem = mod;
                    dev.FirmwareVersion = firmwareVersion;
                    dev.ServerName = serverName;
                    dev.Name = name;
                    dev.Location = location;

                    if(dev != null)
                    {
                        dev.TxBitrate = dev.Modem.TxBitrate;//.ToString();
                        dev.RxBitrate = dev.Modem.RxBitrate;//.ToString();
                        dev.LowBitrate = Math.Min(dev.Modem.TxBitrate, dev.Modem.RxBitrate);
                    }
                    return dev;
                }
            }
            return null;
        }

        public bool ChangeAccessType(ModemAccessType accessType)
        {
            bool success = Modem.ChangeAccessType(accessType);
            if (success == true)
            {
                if (accessType == ModemAccessType.Master)
                {
                    ServerName = Modem.GetAccessTypeName(accessType);
                }
                else if (accessType == ModemAccessType.Repeater)
                {
                    ServerName = Modem.GetAccessTypeName(accessType);
                }
            }
            return success;
        }

        public bool ChangeCouplingType(ModemCouplingType couplingType)
        {
            return Modem.ChangeCouplingType(couplingType);
        }

        public string Read(string uri)
        {
            BatchOutObjects = null;
            BatchOutObjects = Read(new string[] { uri });

            string val = null;

            if (BatchOutObjects == null)
                return null;

            if (BatchOutObjects.Count == 1)
                val = Value(uri);

            return val;
        }

		public List<ObixObject> Read(string[] uris)
		{
			ObixClient client = new ObixClient(Ip, DS2Modem.TunnelPort);
			BatchInObjects = new List<ObixObject>();

			foreach (string s in uris)
			{
				//ObjectsDict[s].Request = ObixRequest.Read;
				ObixObject o = ObixObject.ObjectDict[s];
				o.Request = ObixRequest.Read;
				BatchInObjects.Add(o);
			}
            
            List<ObixObject> batchOut = null;

            if(BatchInObjects.Count > 1)
			    batchOut = client.SendBatch(BatchInObjects);
            else if (BatchInObjects.Count == 1)
            {
                ObixObject o = client.Get(BatchInObjects[0]);
                if (o != null)
                {
                    batchOut = new List<ObixObject>();
                    batchOut.Add(o);
                }
            }
            ErrorMessage = client.GetErrorMessage();
           //if (ErrorMessage != "")
           //     throw new Exception(ErrorMessage);
			return batchOut;
		}

        static public List<ObixObject> Read(IPAddress ip, string[] uris)
        {
            ObixClient client = new ObixClient(ip, DS2Modem.TunnelPort);
            List<ObixObject> inObjects = new List<ObixObject>();

            foreach (string s in uris)
            {
                //ObjectsDict[s].Request = ObixRequest.Read;
                ObixObject o = ObixObject.ObjectDict[s];
                o.Request = ObixRequest.Read;
                inObjects.Add(o);
            }

            List<ObixObject> batchOut = null;

            if (inObjects.Count > 1)
                batchOut = client.SendBatch(inObjects);
            else if (inObjects.Count == 1)
            {
                ObixObject o = client.Get(inObjects[0]);
                if (o != null)
                {
                    batchOut = new List<ObixObject>();
                    batchOut.Add(o);
                }
            }           

            return batchOut;
        }

        public bool Write(string uri, string val)
        {
            ObixEntry[] obixEntries = new ObixEntry[] { new ObixEntry(uri, val) };
            BatchOutObjects = null;
            BatchOutObjects = Write(obixEntries);
            if (BatchOutObjects != null)
                if (val == Value(uri))
                    return true;

            return false;
        }

        public List<ObixObject> Write(ObixEntry[] entries)
        {
            BatchOutObjects = null;
            ErrorMessage = "";
            ObixClient client = new ObixClient(Ip, DS2Modem.TunnelPort);
            BatchInObjects = new List<ObixObject>();

            foreach (ObixEntry ent in entries)
            {
                if (ent.Value.Length > 0)
                {
                    //ObjectsDict[s].Request = ObixRequest.Read;
                    ObixObject o = ObixObject.ObjectDict[ent.Uri];
                    o.Request = ObixRequest.Write;
                    o.Value = ent.Value;
                    BatchInObjects.Add(o);
                }
            }
            List<ObixObject> batchOut = client.SendBatch(BatchInObjects);

            ErrorMessage = client.GetErrorMessage();

            //if (ErrorMessage != "")
            //    throw new Exception(ErrorMessage);
            BatchOutObjects = batchOut;
            return batchOut;
           
        }

        public bool Upgrade()
        {

            try
            {
                EraseFirmware();

                Thread.Sleep(2500); // wait for device boot end
                UpdateCompleteFirmware();
                string ver = "";
                Thread.Sleep(4000);
                for (int tries = 0; tries < 10; tries++)
                {
                    try
                    {
                        ver = Read("/obix/firmwareVersion/");
                        if (ver != "" && ver != null)
                            break;
                    }
                    catch (Exception)
                    {
                        ver = "";
                        Thread.Sleep(30000);
                    }
                }
                FirmwareVersion = ver;
                if (FirmwareVersion == "" || FirmwareVersion == null)
                    return false;
            }
            catch (Exception)
            {
                // MessageBox.Show(ex.ToString());
                return false;
            }            
            return true;
        }

        private string BuildUrl(int port, string path)
        {
            return "http://" + Ip.ToString() + ":" + port.ToString() + path;
        }

        private void EraseFirmware()
        {
            string postBody;
            if (this.ServerName == "Y1G" || this.ServerName == "YFM")
                postBody = "<list of=\"obix:uri\" is=\"obix:BatchIn\">\r\n<uri is=\"obix:Write\" val=\"/obix/erase/\">\r\n<bool name=\"in\" val=\"true\" /></uri>\r\n</list>\r\n";
                //postBody = "<list of=\"obix:uri\" is=\"obix:BatchIn\">\r\n<uri is=\"obix:Write\" val=\"/obix/erase/\">\r\n<bool name=\"in\" val=\"true\" />\r\n</uri>\r\n</list>\r\n";
            else
                throw new Exception("Firmware update not supported on this server");
            string url = BuildUrl(DS2Modem.TunnelPort, "/obix/batch/");
            int tries = 0;
            while (true)
            {
                try
                {

                    HttpRequest request = new HttpRequest(url);
                    request.Post(postBody);
                    string response = request.ResponseToString();
                    if (response != null)
                        if (response == "rebooting" || response.StartsWith("<list of=\"obix:obj\" is=\"obix:BatchOut\">")
                            || response.StartsWith("success"))
                            return;
                    tries++;
                }
                catch (Exception)
                {
                    tries++;
                }
                if (tries >= 10)
                    throw new Exception("Firmware erassing has failed"); ;
            }
        }
        
        private void UpdateCompleteFirmware()
        {
            string url = BuildUrl(DS2Modem.TunnelPort, "/obix/update/");
            string postBody = "";
            int j = 0;
            for (int i = 0; i < UpgradeLines.Length; i++ )
            {
                j++;
                postBody += UpgradeLines[i] + "\r\n";
                if (j >= 10 || ( i == (UpgradeLines.Length-1)))
                {
                    DoUpgradeFirmware(url, postBody);
                    if (YDeviceUpgradeProgressed != null)
                    {
                        YDeviceUpdateProgressedEventArgs progArg = new YDeviceUpdateProgressedEventArgs();
                        progArg.LinesUpgraded = j;
                        YDeviceUpgradeProgressed(this, progArg);
                    }
                    j = 0;
                    postBody = "";
                }
            }
        }

        int _upgradeFailMaxPause = 250;
        int _upgradeFailMinPause = 50;

        private void DoUpgradeFirmware(string url, string upgradeData)
        {
            int tries = 0;

            while (true)
            {
                try
                {
                    HttpRequest request = new HttpRequest(url);
                    request.Post(upgradeData);
                    string response = request.ResponseToString();
                    if (response.StartsWith("success"))
                        return;
                    tries++;
                }
                catch (Exception)
                {
                    tries++;
                }
                if (tries >= 100) // TODO set back to ten
                    throw new Exception("Firmware update has failed");
                Random random = new Random();
                int delay = random.Next(_upgradeFailMinPause, _upgradeFailMaxPause);
                Thread.Sleep(delay);
            }
        }

        public void ReadYfmConfig()
        {
            string[] uris = new string[] {  "/obix/name/", "/obix/location/", "/obix/yfm/relay1/offDelay/", 
                                            "/obix/yfm/relay1/flickReps/", "/obix/yfm/relay1/autoMode/"};
            string error = "";
            BatchOutObjects = null;
            BatchOutObjects = Read(uris);
            if (ErrorMessage != null && ErrorMessage != "")
                return;

            if (BatchOutObjects == null || BatchOutObjects.Count == 0)
                return;

            uris = new string[] { "/obix/yfm/dimming/dimMode/", "/obix/yfm/dimming/dim/", "/obix/yfm/dimming/dimMin/" };

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported"))
                error += ErrorMessage;

            uris = new string[] { "/obix/yfm/motionMuteDelay/" };

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("24V output is not installed"))
                error += ErrorMessage;

            uris = new string[] {  "/obix/yfm/relay2/offDelay/", "/obix/yfm/relay2/flickReps/", "/obix/yfm/relay2/autoMode/"};

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported") )
                error += ErrorMessage;

            uris = new string[] {  "/obix/yfm/relay3/offDelay/", "/obix/yfm/relay3/flickReps/", "/obix/yfm/relay3/autoMode/"};

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported"))
                error += ErrorMessage;

            uris = new string[] {  "/obix/yfm/relay4/offDelay/", "/obix/yfm/relay4/flickReps/", "/obix/yfm/relay4/autoMode/"};

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported"))
                error += ErrorMessage;

            ErrorMessage = error;
        }

        public void ReadYfmData()
        {
            string[] uris = new string[] {  "/obix/yfm/relay1/switchStatus/", "/obix/yfm/relay1/latchActive/",
                                            "/obix/yfm/bPressLapse/"};
            BatchOutObjects = null;
            BatchOutObjects = Read(uris);

            string error = "";

            if (ErrorMessage != null && ErrorMessage != "")
                return;

            if (BatchOutObjects == null || BatchOutObjects.Count == 0)
                return;

            uris = new string[] {  "/obix/yfm/relay2/switchStatus/", "/obix/yfm/relay2/latchActive/"};

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported"))
                error += ErrorMessage;

            uris = new string[] { "/obix/yfm/relay3/switchStatus/", "/obix/yfm/relay3/latchActive/",
                                  "/obix/yfm/relay4/switchStatus/", "/obix/yfm/relay4/latchActive/"};

            BatchOutObjects.AddRange(Read(uris));

            if (ErrorMessage != null && ErrorMessage != "" && !ErrorMessage.Contains("Not supported"))
                error += ErrorMessage;

            ErrorMessage = error;
        }

        public string Value(string uri)
        {
            if (BatchOutObjects != null)
            {
                foreach (ObixObject o in BatchOutObjects)
                    if (uri == GetRelativeUri(o.Uri))
                        return o.Value;
            }
            return "";
        }

        private string GetRelativeUri(string uri)
        {
            string uriBase = "http://" + Ip.ToString() + ":40044";
            if (uri.StartsWith(uriBase))
                return uri.Substring(uriBase.Length);
            else
                return uri;
        }

        public string BuildInfoString()
        {
            string infoStr = Modem.MacAddress + ",";
            infoStr += Modem.IpAddress + ",";
            infoStr += ServerName + ",";
            infoStr += Modem.GetCouplingName() + ",";
            infoStr += Name + ",";
            infoStr += Location + ",";
            infoStr += FirmwareVersion + ",";
            infoStr += TxBitrate.ToString() + ",";
            infoStr += RxBitrate.ToString() + ",";
            infoStr += Ping + "\r\n";
            return infoStr;
        }
	}
}
